﻿namespace Legend.Core.Tests
{
    using System;
    using System.IO;
    using NUnit.Framework;
    using System.Text;
    using System.Linq;
    using FakeItEasy;
    using Legend.Tests;
    using System.Collections.Generic;

    [TestFixture]
    public class IndentedTextWriterTests
    {
        [Test]
        public void Should_write_text_to_output()
        {
            // Arrange
            var output = new StringBuilder();
            var writer = CreateWriter("  ", output);

            // Act
            writer.Write("foo");

            // Assert
            Assert.That(output.ToString(), Is.EqualTo("foo"));
        }

        [Test]
        public void Should_write_text_indented()
        { 
            // Arrange
            var output = new StringBuilder();
            var writer = CreateWriter("  ", output);

            // Act
            using (writer.Indent())
            {
                writer.Write("foo");
            }
            
            // Assert
            Assert.That(output.ToString(), Is.EqualTo("  foo"));
        }

        [Test]
        public void Should_write_text_after_new_line_indented()
        {
            // Arrange
            var output = new StringBuilder();
            var writer = CreateWriter("  ", output);

            // Act
            using (writer.Indent())
            {
                writer.Write("foo\r\nbar");
            }
            
            // Assert
            Assert.That(output.ToString(), Is.EqualTo("  foo\r\n  bar"));
        }

     

        [Test]
        public void Should_write_double_indentation_when_indenting_twice()
        {
            // Arrange
            var output = new StringBuilder();
            var writer = CreateWriter("  ", output);

            // Act
            using (writer.Indent())
            using (writer.Indent())
            {
                writer.Write("foo");
            }
            
            // Assert
            Assert.That(output.ToString(), Is.EqualTo("    foo"));
        }

        [Test]
        public void Should_get_encoding_from_wrapped_writer()
        { 
            // Arrange
            var wrapped = A.Fake<TextWriter>();
            A.CallTo(() => wrapped.Encoding).Returns(Encoding.ASCII);

            var writer = CreateWriter(string.Empty, wrapped);

            // Act, Assert
            Assert.That(writer.Encoding, Is.EqualTo(Encoding.ASCII));
        }

        [Test]
        public void Should_unindent_a_level_when_disposing_object_returned_from_indent()
        { 
            // Arrange
            var output = new StringBuilder();
            var writer = CreateWriter("  ", output);
            writer.Indent().Dispose();

            // Act
            writer.Write("foo");

            // Assert
            Assert.That(output.ToString(), Is.EqualTo("foo"));
        }

        [Test]
        public void Should_use_four_spaces_as_default_indentation()
        { 
            // Arrange
            var output = new StringBuilder();
            var writer = new IndentedTextWriter(new StringWriter(output));

            // Act
            using (writer.Indent())
            {
                writer.Write("foo");
            }
            
            // Assert
            Assert.That(output.ToString(), Is.EqualTo("    foo"));
        }

        [Test]
        public void Should_not_unindent_more_than_one_level_when_disposing_object_returned_from_indent_twice()
        {
            // Arrange
            var output = new StringBuilder();
            var writer = CreateWriter(">", output);
            // Act

            using (writer.Indent())
            {
                using (var indent = writer.Indent())
                {
                    indent.Dispose();
                }

                writer.Write("foo");
            }
            
            // Assert
            Assert.That(output.ToString(), Is.EqualTo(">foo"));
        }

        [Test]
        public void Should_dispose_wrapped_writer_when_disposing()
        { 
            // Arrange
            var wrapped = A.Fake<TextWriter>();
            var writer = new IndentedTextWriter(wrapped);

            // Act
            writer.Dispose();

            // Assert
            A.CallTo(wrapped).Where(x => x.Method.Name.Equals("Dispose")).MustHaveHappened();
        }

        [Test]
        public void Should_flush_wrapped_writer()
        {
            // Arrange
            var wrapped = A.Fake<TextWriter>();
            var writer = new IndentedTextWriter(wrapped);

            // Act
            writer.Flush();

            // Assert
            A.CallTo(() => wrapped.Flush()).MustHaveHappened();
        }

        [Test]
        public void Should_have_null_guarded_constructors()
        {
            NullGuardedConstraint.Assert(() => new IndentedTextWriter(new StringWriter()));
            NullGuardedConstraint.Assert(() => new IndentedTextWriter("    ", new StringWriter()));
        }

        public IEnumerable<Action<TextWriter>> DelegatedCalls
        {
            get 
            {
                yield return x => x.Close();
                yield return x => x.Dispose();
                yield return x => x.Flush();
                yield return x => { var _ = x.FormatProvider; };
                yield return x => { var _ = x.NewLine; };
                yield return x => x.NewLine = "\r\n";
                yield return x => x.ToString();
            }
        }

        [TestCaseSource("DelegatedCalls")]
        public void Should_delegateCalls_to_wrapped(Action<TextWriter> call)
        {
            // Arrange
            var wrapped = A.Fake<TextWriter>();
            var writer = new IndentedTextWriter(wrapped);

            // Act
            call(writer);

            // Assert
            NextCall.To(wrapped).MustHaveHappened();
            call(wrapped);
        }

        [Test]
        public void Should_write_to_string_builder_thats_passed_to_constructor()
        { 
            // Arrange
            var output = new StringBuilder();
            var writer = new IndentedTextWriter(output);

            // Act
            writer.Write("foo");

            // Assert
            Assert.That(output.ToString(), Is.EqualTo("foo"));
        }

        [Test]
        public void Should_be_able_to_indent_console()
        { 
            // Arrange
            var output = new StringWriter();
            Console.SetOut(output);

            // Act
            var indenter = IndentedTextWriter.MakeConsoleIndentable();
            
            using (indenter.Indent())
            {
                Console.WriteLine("foo");

                using (indenter.Indent())
                {
                    Console.WriteLine("bar");
                }

                Console.WriteLine("biz");
            }

            // Assert
            Assert.That(output.ToString(), Is.EqualTo(@"    foo
        bar
    biz
"));
        }

        private IndentedTextWriter CreateWriter(string indentation, StringBuilder output)
        {
            return this.CreateWriter(indentation, new StringWriter(output));
        }

        private IndentedTextWriter CreateWriter(string indentation, TextWriter output)
        {
            return new IndentedTextWriter(indentation, output);
        }
    }
}
